
/* GCSx
** LOAD.H
**
** File loading only
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#ifndef __GCSx_LOAD_H_
#define __GCSx_LOAD_H_

// Structures used internally
struct BlockHeader {
    Uint32 version; // 0 if not read
    Uint32 headerSize;
    Uint32 contentSize;
    Uint32 compression;
};

struct BlockInfo {
    // Current info
    Uint32 offset; // 0 if never written to file
    Uint32 size;
    Uint32 type;
    class LoadOnly* object; // Our object that can read this block
    BlockHeader header; // Header, if not read, version is 0
    
    // New info used when saving to a new file so we don't
    // overwrite old info until save is confirmed
    Uint32 newOffset;
    Uint32 newSize;
    BlockHeader newHeader;
};

// Interface for our files that we pass to objects- no seeking except rewind

class FileRead {
private:
    class File* filePtr;
    int startOffset;
    int totalSize;
    Uint32 version;
    Uint32 compression;
    int currentOffset;
    int bytesRead;

    // For readStr
    enum {
        COPY_BUFFER_SIZE = 8192,
    };
    static char copyBuffer[COPY_BUFFER_SIZE + 1];

    // ZLIB compression method
    z_stream* zlibStream;
    int zlibStreamInit;
    Byte* zlibBufferIn;
    Uint32 zlibOrigSize;
    enum { ZLIB_BUFFER_SIZE = 8192 };
    void zlibNextBuffer() throw_File;

public:
    // Doesn't assume filePtr is seeked properly or anything like that, on ANY read
    FileRead(class File* myFilePtr, int offset, int size, Uint32 myVersion, Uint32 myCompression) throw_File;
    ~FileRead();
    
    // What version of data?
    Uint32 getVersion() const { return version; }
    
    // How large is our data, total? (uncompressed)
    int getSize() const;
    // Any more data for us? (actually returns total bytes remaining)
    int moreData() const;

    // Read X more bytes; throws an exception if size is greater than the bytes
    // we've been allocated to read
    void read(void* ptr, int size) throw_File;
    Uint32 readInt() throw_File;
    void readIntBulk(Uint32* ptr, int count) throw_File;
    Uint16 readInt16() throw_File;
    Uint8 readInt8() throw_File;
    // NON-REENTRANT
    void readStr(std::string& str) throw_File;
    void rewind() throw_File;
};

// Interface for objects that just load

class LoadOnly {
public:
    // Generic informational functions, so we can also use LoadOnly as
    // a generic "GCSx object" supertype
    virtual int getId() const = 0;
    // Returns BLOCKTYPE_UNUSED if not a directly writable object
    virtual int getBlockType() const = 0;
    
    // Load header
    // Discard FileRead object when done
    virtual void loadHeader(class FileRead* file) = 0;
    
    // Content, by choice of the object, can be cached
    // The FileRead pointer will be good any time the object
    // wishes to load as long as the object doesn't modify
    // itself; object MUST DEALLOCATE this pointer when done
    // Object MUST DEALLOCATE even if exception/throw!
    virtual void loadContent(class FileRead* file) = 0;
    // Typically used internally, and if excepts, should leave uncached entirely
    virtual void cacheLoad() = 0; // (ensures not cached)
    virtual int isContentCached() const = 0;
    
    // Mark or unmark an object as being used; lock should
    // be incremental; return current lock value, > 0 is locked,
    // 0 is locked. This should also load from cache. Content
    // cannot be returned to cache until unlocked down to 0.
    // This is the only required use of lock (to prevent cacheing)
    // Other functions do NOT need to check cache- outside sources
    // should call markLock appropriately.
    virtual int markLock() = 0;
    virtual int markUnlock() = 0;
    virtual int isLocked() const = 0;
};

// World file storage- All data is stored LITTLE ENDIAN
//   Bytes 00-03 GCSx
//   Bytes 04-19 Version, 4 int32s major minor bugfix build
//   Bytes 20-23 Reserved (may become pointer within file to world info block)
//   Bytes 24-27 Reserved
//   Bytes 28-31 Number of blocks
// Per block-
//   Bytes 00-03 Offset, 0 if no block in this spot
//   Bytes 04-07 Overall block size as stored, 0 if no block in this spot
//   Bytes 08-11 Block type, 0 if no block or if block is unused and available
//   Bytes 12-15 Reserved
// At block offset-
//   Bytes 00-03 Block version code
//   Bytes 04-07 Size of header data
//   Bytes 08-11 Size of (compressed) content data
//   Bytes 12-15 Compression type (applies to content only)
//   Bytes 16-   Header data, followed by content data

class WorldFileLoad {
protected:
    Uint32 fileVersion[4];
    Uint32 numBlocks;
    std::vector<BlockInfo> blocks; // BlockInfo id is index
    class File* filePtr;
    const std::string* filename;
    Uint32 scanNextBlock;
    
    static const char* correctCookie;
    static const Uint32 currentVersion[4];
    
    WorldFileLoad();
    
public:
    // Block types
    enum {
        // Unused blocks can't be claimed, but their position/size
        // are still relevant
        // Do not change the order/numbering of these!
        BLOCKTYPE_UNUSED = 0,
        BLOCKTYPE_WORLDINFO = 1,
        BLOCKTYPE_TILESET = 2,
        BLOCKTYPE_SCENE = 3,
        BLOCKTYPE_ANIMGROUP = 4,
        BLOCKTYPE_SCRIPT = 5,
        BLOCKTYPE_FOLDER = 6,
    };
    
    // Compression types
    enum {
        BLOCKCOMPRESSION_NONE = 0,
        BLOCKCOMPRESSION_ZLIB = 1,
    };

    // Pointer must remain valid
    WorldFileLoad(const std::string* openFile, int mode = File::FILE_MODE_READ) throw_File; // Open existing
    virtual ~WorldFileLoad(); // Closes the file
    
    // Use this to loop through all in-use blocks, to load
    // This initializes or "rewinds" the nextBlock() sequence.
    // You may loop through as many times as needed, but cannot claimBlock more
    // than once per block
    void scanBlocks();
    
    // Returns true if there was a "next" block- returns type and
    // a unique ID for the next block; construct object and tell
    // the object to load the block by it's ID; This skips unused blocks
    int nextBlock(Uint32* getType, Uint32* getID);
    
    // An object uses this to "claim" a block by it's ID; it will
    // then have both load functions called; cannot claim an id
    // more than once
    void claimBlock(Uint32 id, LoadOnly* claimingObject) throw_File;
};

#endif

